import fs from "fs-extra"
import path from "node:path"
import chalk from "chalk";
import Config from "./ssg.config.js"
import { getDirName, removeDir, invokeWithErrorHandling, createFile, copyFile, copyDir, progress, dirIsExist, isImg, fileIsExist, renameDir, warn, success } from "./util.js"
import type { ConfigItem } from "./ssg.config"

export default class Creator {
  configs = Config;
  __dirname = getDirName();
  PROJECT_PATH = path.resolve(this.__dirname, "../projects");
  IMGS_PATH = path.resolve(this.__dirname, "../imgs");
  TEMPLATE_PATH = path.resolve(this.__dirname, "../template");
  ROOT = ""
  filters = ["index.md", "assets"]    // 全局过滤文件夹
  warnList: string[] = [];

  // 1) 开始执行
  public start() {
    // 1 创建 projects 文件夹
    fs.ensureDirSync(this.PROJECT_PATH)
    // 2 循环创建
    Object.keys(this.configs).forEach(key => this.generate(key, this.configs[key]))
    // 3 成功提示
    console.log(chalk.green("SUCCESS Generation"));
  }

  // 2) 创建对应文件夹
  private generate(proName: string, config: ConfigItem) {
    // 1 获取根目录
    this.ROOT = path.resolve(this.PROJECT_PATH, `./${proName}`)
    const progressor = progress([
      `${chalk.yellow(proName)}: start building...`,
      `${chalk.yellow(proName)}: remove originDir and create new dir`,
      `${chalk.yellow(proName)}: generate package.json`,
      `${chalk.yellow(proName)}: create docs folder`,
      `${chalk.yellow(proName)}: change docs folder name`,
      `${chalk.yellow(proName)}: crate .vitepress folder`,
      `${chalk.yellow(proName)}: generate theme folder`,
      `${chalk.yellow(proName)}: create public folder`,
      `${chalk.yellow(proName)}: create index.md for every doc directory`,
      `${chalk.yellow(proName)}: create home index.md file`,
      `${chalk.yellow(proName)}: create config.ts file`,
      `${chalk.yellow(proName)}: create server folder`,
      `${chalk.yellow(proName)}: copy *.vue to the docs folder`,
      `${chalk.yellow(proName)}: ${chalk.blue('Create Success!')}:`,
    ])

    // 2 清除文件夹再重新创建
    progressor.next()
    removeDir(this.ROOT, ['node_modules']) && fs.ensureDirSync(this.ROOT)

    // 3 初始化配置文件
    progressor.next()
    this.initPackage(proName, config.port)

    // 3 生成 docs 文件夹
    progressor.next()
    fs.ensureDirSync(path.resolve(this.ROOT, './docs'))

    // 4 初始化 docs 数据
    progressor.next()
    this.initDocs(proName, config)

    // 5 修改 docs 中文件夹命名（不能是中文）
    progressor.next()
    this.initDocsName(proName, config)

    // 6 生成 .vitepress 文件夹
    progressor.next()
    fs.ensureDirSync(path.resolve(this.ROOT, './docs/.vitepress'))

    // 7 初始化 theme 文件夹
    progressor.next()
    this.initTheme(config)

    // 8 初始化 public 文件夹，存放图片资源
    progressor.next()
    this.initPublic(proName)

    // 9 为每一个 doc 目录创建 index.md
    progressor.next()
    this.initIndexMd(config)

    // 10 初始化首页 index.md
    progressor.next()
    this.initHomeIndexMd(config)

    // 11 初始化 config.ts
    progressor.next()
    this.initConfigFile(proName, config)

    // 12 初始化 server 文件夹
    progressor.next()
    this.initServer(config)

    // 13 初始化 *.vue
    progressor.next()
    this.initVueCom()
    progressor.next()
  }
  private initPackage(proName: string, port: number) {
    if (port) {
      copyFile(path.resolve(this.TEMPLATE_PATH, "./package.json"), path.resolve(this.ROOT, "./package.json"), "", (data: string) => {
        let filter: Record<string, string> = {
          "/* NAME */": proName,
          '/* PORT */': String(port),
        }
        Object.keys(filter).forEach(matchKey => {
          data = data.replaceAll(matchKey, filter[matchKey])
        })
        return data
      })
    } else {
      throw new Error(`unset <<< port >>> in ${proName} project`)
    }
  }
  private initDocs(proName: string, config: ConfigItem) {
    if (config.folderName) {
      const DOCS_FILES_PATH = path.resolve(this.__dirname, "../docs/前端修炼笔记/", config.folderName)
      const DOCS_DEST_PATH = path.resolve(this.ROOT, "./docs")

      // 1 校验对应映射文件夹
      if (!dirIsExist(DOCS_FILES_PATH)) {
        throw new Error(`create ${proName} project path errror: ${DOCS_FILES_PATH} is unexist`)
      }

      // 2 内部全是文件，直接复制
      if (!config?.sliders?.unFolder) {
        copyDir(DOCS_FILES_PATH, DOCS_DEST_PATH, (data) => {
          return this.hanldeData(data)
        })
      }

      // 3 内部有文件夹，生成文件夹，并将文件复制过去
      else {
        if (!config.sliders.en) throw new Error(`unset <<< sliders.en >>> in ${proName} project`);
        fs.ensureDirSync(`${DOCS_DEST_PATH}/${config.sliders.en}`)
        copyDir(DOCS_FILES_PATH, `${DOCS_DEST_PATH}/${config.sliders.en}`, (data) => {
          return this.hanldeData(data)
        })
      }
    } else {
      throw new Error(`unset <<< path >>> in ${proName} project`)
    }
  }
  private hanldeData(data: string): string {
    // 1 获取默认配置
    const optionReg = /^\s*---([\w\W]+?)---/
    const optionMatch = data.match(optionReg) || []
    // 2 添加 outline: "deep" 显示二级标题
    let optionRes = `---\r\noutline: "deep"\r\n`
    if (optionMatch[0]) {
      data = data.replace(optionMatch[0], "")
      let inject = optionMatch[1]
      if (!inject.includes("outline")) {
        optionRes += inject
      }
    }
    optionRes += "---\r\n"
    data = `${optionRes}${data}`
    return data
  }
  private initDocsName(proName: string, config: ConfigItem) {
    if (!config?.sliders?.unFolder) {
      if (config.sliders) {
        const sliders = config.sliders
        const ROOT_DOCS_PATH = path.resolve(this.ROOT, './docs')
        Object.keys(sliders).forEach(folderName => {
          let val = sliders[folderName]
          if (!val.en) {
            throw new Error(`unset <<< ${folderName}.en >>> in ${proName} project`)
          }
          let newPath = path.resolve(ROOT_DOCS_PATH, folderName)
          let oldPath = path.resolve(ROOT_DOCS_PATH, val.en)
          renameDir(newPath, oldPath)
        })
      } else {
        throw new Error(`unset <<< sliders >>> in ${proName} project`)
      }
    }

  }
  private initTheme(config: ConfigItem) {
    let filterArr = ["index.ts"]
    let replaceStr: Record<string, any> = {
      "/* nav-style */": config["nav-style"] || `font-size: 14px;`,
      '/* img-style */': config["img-style"],
      '/* name-style */': config["hero"]["name-style"],
      '/* text-style */': config["hero"]["text-style"],
      '/* root-style */': config["root-style"],
      "/* github-show */": config.github ? "" : { display: 'none !important' }
    }
    copyDir(path.resolve(this.TEMPLATE_PATH, "./theme"), path.resolve(this.ROOT, "./docs/.vitepress/theme"), (data, filename) => {
      if (filterArr.includes(filename)) {
        return data
      }
      else {
        for (const replaceKey in replaceStr) {
          let val = replaceStr[replaceKey]
          // 样式统一加个 ;
          let str = ""
          Object.keys(val).forEach(styleKey => {
            str += `${styleKey}: ${val[styleKey]};`
          })
          data = data.replaceAll(replaceKey, str)
        }
      }
      return data
    })
  }
  private initPublic(proName: string) {
    // 1 获取路径
    const DOCS_PUBLIC_PATH = path.resolve(this.ROOT, './docs/public')

    // 2 确保文件夹存在
    fs.ensureDirSync(DOCS_PUBLIC_PATH)

    // 3 获取图片资源
    let imgs = fs.readdirSync(this.IMGS_PATH)
    let matchKey = new RegExp(`(${proName})|(${proName.toLocaleLowerCase()})`, "g")
    let hasFavicon = false

    // 4 遍历，将对应项目的图标复制过去
    for (const img of imgs) {
      if (matchKey.test(img)) {
        copyFile(`${this.IMGS_PATH}/${img}`, `${DOCS_PUBLIC_PATH}/favicon.svg`, "")
        hasFavicon = true
        break;
      }
    }
    if (!hasFavicon) {
      throw new Error(`unset <<< favicon.svg >>> in ${proName} project, please check your imgs`)
    }
  }
  private initIndexMd(config: ConfigItem) {
    // 1 设置路径
    const docsPath = path.resolve(this.ROOT, './docs')

    // 2 存在文件夹
    if (config.sliders) {
      const sliders = config.sliders
      Object.keys(sliders).forEach(sliderKey => {
        let slider = sliders[sliderKey]
        let folderName = slider.en
        let destPath = path.resolve(docsPath, folderName)
        let start = slider.start || ""
        let end = slider.end || ""
        let str = ""
        let files = fs.readdirSync(destPath)
        // 2 遍历文件夹
        files.forEach((fileName, index) => {
          if (this.filters.includes(fileName)) {
            // 内部已经有 index.md 给出警告
            if (fileName === "index.md") {
              this.warnList.push(`There is a index.md in ${folderName} at ${docsPath}`)
            }
            return
          }
          str += `[${fileName.replace(/.md$/, '')}](/${folderName}/${fileName})<br />`
        })
        str = `${start}\n\n${str}\n\n${end}`
        createFile(`${destPath}/index.md`, str)
      })
    }
    // 3 只存在文件
    if (config.slider) {
      let slider = config.slider
      let folderName = slider.en || ""
      let destPath = path.resolve(docsPath, folderName)
      let start = slider.start || ""
      let end = slider.end || ""
      let str = ""
      let files = fs.readdirSync(destPath)
      files.forEach((fileName, index) => {
        if (this.filters.includes(fileName)) {
          // 内部已经有 index.md 给出警告
          if (fileName === "index.md") {
            this.warnList.push(`There is a index.md in ${folderName} at ${docsPath}`)
          }
          return
        }
        str += `[${fileName.replace(/.md$/, '')}](/${folderName}/${fileName})<br />`
      })
      str = `${start}\n\n${str}\n\n${end}`
      createFile(`${destPath}/index.md`, str)
    }
  }
  private initHomeIndexMd(config: ConfigItem) {
    // 1 获取 dest 路径
    const DOCS_INDEX_MD_PATH = path.resolve(this.ROOT, './docs/index.md')
    const TEMPLATE_INDEX_MD_PATH = path.resolve(this.TEMPLATE_PATH, "./index.md")

    // 2 待替换字符串
    let replaceStr: Record<string, any> = {
      "#name": config.hero.name,
      '#text': config.hero.text,
      '#tagline': config.hero.tagline,
      '#actions': this.getActions(config.hero.actions),
      '#features': this.getFeatures(config.features)
    }
    copyFile(TEMPLATE_INDEX_MD_PATH, DOCS_INDEX_MD_PATH, "", data => {
      for (const replaceKey in replaceStr) {
        let val = replaceStr[replaceKey]
        data = data.replaceAll(replaceKey, val)
      }
      return data
    })
  }
  private getActions(actions: Record<string, any>[]): string {
    let res = ""
    actions.forEach((v, i) => {
      if (i === 0) {
        res += `- theme: ${v.theme}
      text: ${v.text}
      link: ${v.link}\n`
      }
      else {
        res += `    - theme: ${v.theme}
      text: ${v.text}
      link: ${v.link}\n`
      }
    })
    return res
  }
  private getFeatures(features: Record<string, any>[]): string {
    let res = ""
    features.forEach((v, i) => {
      if (i === 0) {
        res += `- icon: ${v.icon}
    title: ${v.title}
    details: ${v.details}\n`
      }
      else {
        res += `  - icon: ${v.icon}
    title: ${v.title}
    details: ${v.details}\n`
      }
    })
    return res
  }
  private initConfigFile(proName: string, config: ConfigItem) {
    // 1 检验字段
    if (!config.sliders) {
      throw new Error(`unset <<< sliders >>> in ${proName} project`)
    }
    // 2 获取属性
    const nav = this.getNav(config)
    const sidebar = this.getSidebar(config)
    const footer = config.footer || {
      message: "Released under the MulanPSL-1.0 License.",
      copyright: `Copyright © ${new Date().getFullYear()}-present sishen`,
    }
    // 3 创建文件
    const congfig = {
      title: proName,
      titleTemplate: false,
      head: [["link", { rel: "icon", href: "/favicon.svg" }]],
      lastUpdated: true,
      lang: "en-US",
      themeConfig: {
        siteTitle: "TypeScript",
        logo: "/favicon.svg",
        socialLinks: [
          { icon: "github", link: config.github },
        ],
        nav, footer, sidebar
      }
    }
    createFile(path.resolve(this.ROOT, './docs/.vitepress/config.ts'), `export default ${JSON.stringify(congfig)}`)
  }
  private getNav(config: ConfigItem) {
    let nav: Record<string, any>[] = []
    // 1 只存在文件夹
    if (config.sliders) {
      const sliders = config.sliders
      Object.keys(sliders).forEach(key => {
        let val = sliders[key]
        let text = key.replace(/^\d+-/, "")
        let link = `/${val.en}/`
        nav.push({ text, link })
      })
    }
    // 2 只存在文件
    if (config.slider) {
      let val = config.slider
      let text = val.sliderName
      let link = `/${val.en}/`
      nav.push({ text, link })
    }
    // 3 额外导航栏
    if (config["other-nav"]) {
      const otherNav = config["other-nav"]
      Object.keys(otherNav).forEach(key => {
        let val = otherNav[key]
        let text = key.replace(/^\d+-/, "")
        if (Array.isArray(val)) {
          nav.push({ text, items: val })
        } else {
          nav.push({ text, link: val })
        }
      })
    }
    return nav
  }
  private getSidebar(config: ConfigItem) {
    const sidebar: Record<string, any> = {}
    const docsPath = path.resolve(this.ROOT, './docs')

    // 1 只存在文件夹
    if (config.sliders) {
      const sliders = config.sliders
      Object.keys(sliders).forEach(sliderKey => {
        let val = sliders[sliderKey]
        let key = `/${val.en}/`
        let files = fs.readdirSync(path.resolve(docsPath, val.en))
        let items: Record<string, any>[] = []
        files.forEach(file => {
          if (this.filters.includes(file)) return;
          items.push({
            text: file.replace(/\.md$/, ""),
            link: `${key}${file}`
          })
        })
        sidebar[key] = [{
          text: val.en,
          items: items,
          collapsible: true
        }]
      })
    }
    // 2 只存在文件
    if (config.slider) {
      let val = config.slider
      let key = `/${val.en}/`
      let files = fs.readdirSync(path.resolve(docsPath, val.en))
      let items: Record<string, any>[] = []
      files.forEach(file => {
        if (this.filters.includes(file)) return;
        items.push({
          text: file.replace(/\.md$/, ""),
          link: `${key}${file}`
        })
      })
      sidebar[key] = [{
        text: val.en,
        items: items,
        collapsible: true
      }]
    }
    return sidebar
  }
  private initServer(config: ConfigItem) {
    // 1 复制 server 文件夹
    const ROOT_SERVER_PATH = path.resolve(this.ROOT, './server')
    copyDir(path.resolve(this.TEMPLATE_PATH, './server'), ROOT_SERVER_PATH)
    // 2 修改 .env 中的 PORT
    const data = fs.readFileSync(path.resolve(ROOT_SERVER_PATH, "./.env"))
    fs.writeFileSync(path.resolve(ROOT_SERVER_PATH, "./.env"), data + `${data.length != 0 ? "\n" : ""}PORT=${config.port || "4444"}`, { encoding: "utf-8" })
  }
  private initVueCom() {
    fs.readdirSync(this.TEMPLATE_PATH).forEach(dir => {
      if (path.extname(dir) === ".vue") {
        copyFile(path.resolve(this.TEMPLATE_PATH, dir), path.resolve(this.ROOT, `docs/${dir}`))
      }
    })
  }
}
